﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using Epi.Data;
using CDC.SampleShell.Infrastructure.Events;

namespace CDC.SampleShell.Infrastructure
{
    public class ChangesetChecker : IDisposable
    {
        private const double POLL_MIN = 50;
        private const double POLL_MAX = 600;

        private readonly IDbDriver _database;
        private readonly Timer _updateTimer;
        private double _interval = 500;
        private bool _isCheckingForUpdates = false;
        private readonly string _macAddress = CDC.SampleShell.Common.GetMacAddress();
        private int _currentChangesetId = -1;

        public bool IsEnabled
        {
            get
            {
                return _updateTimer.Enabled;
            }
        }

        /// <summary>
        /// Gets/sets the interval at which the server should be polled for updates
        /// </summary>
        public double Interval
        {
            get
            {
                return _interval;
            }
            set
            {
                Contract.Requires(value >= POLL_MIN);
                Contract.Requires(value <= POLL_MAX);

                _interval = value;
            }
        }

        public ChangesetChecker(IDbDriver database, double pollRate)
        {
            Contract.Requires(database != null);
            Contract.Requires(database.ToString().Contains("Sql", StringComparison.OrdinalIgnoreCase));
            Contract.Requires(pollRate >= POLL_MIN);
            Contract.Requires(pollRate <= POLL_MAX);

            _database = database;

            Query maxChangesetId = _database.CreateQuery("SELECT MAX(ChangesetID) FROM Changesets");
            _currentChangesetId = Convert.ToInt32(_database.ExecuteScalar(maxChangesetId));
            Interval = pollRate;

            _updateTimer = new Timer(Interval);
            _updateTimer.Elapsed += Timer_Elapsed;
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
 	        throw new NotImplementedException();
        }

        private Changeset CreateChangesetFromDataRow(DataRow row)
        {
            // pre
            Contract.Requires(row != null);
            Contract.Requires(row.Table.Columns.Contains("Changeset"));
            Contract.Requires(row.Table.Columns.Contains("Description"));
            Contract.Requires(row.Table.Columns.Contains("ChangesetID"));
            Contract.Requires(row.Table.Columns.Contains("UserID"));
            Contract.Requires(row.Table.Columns.Contains("DestinationRecordId"));
            Contract.Requires(row.Table.Columns.Contains("DestinationFormId"));
            Contract.Requires(row.Table.Columns.Contains("UpdateType"));
            Contract.Requires(row.Table.Columns.Contains("CheckinDate"));

            int changeset = (int)(row["Changeset"]);
            string desc = row["Description"].ToString();
            string guid = row["ChangesetID"].ToString();
            string userID = row["UserID"].ToString();
            Guid recordID = new Guid(row["DestinationRecordId"].ToString());
            int formID = Convert.ToInt32(row["DestinationFormId"]);
            int type = (int)(row["UpdateType"]);
            DateTime checkinDate = (DateTime)(row["CheckinDate"]);
            ChangesetType updateType = (ChangesetType)type;

            Changeset message = new Changeset(changeset, guid, userID, desc, recordID, formID, checkinDate, updateType);
            return message;
        }

        private async void PollServerForUpdatesAsync()
        {
            if (_isCheckingForUpdates) return;

            _isCheckingForUpdates = true;

            await Task.Factory.StartNew(delegate
            {
                try 
                {
                    Query selectQuery = _database.CreateQuery("SELECT * FROM Changesets WHERE MACADDR <> @MACADDR AND ChangesetID > @ChangesetID ORDER BY ChangesetID ASC");
                    selectQuery.Parameters.Add(new QueryParameter("@MACADDR", DbType.String, _macAddress));
                    selectQuery.Parameters.Add(new QueryParameter("@ChangesetId", DbType.Int32, _currentChangesetId));

                    DataTable dt = _database.Select(selectQuery);

                    if (dt.Rows.Count > 0)
                    {
                        Queue<Changeset> changesets = new Queue<Changeset>();

                        foreach (DataRow row in dt.Rows)
                        {
                            changesets.Enqueue(CreateChangesetFromDataRow(row));
                        }

                        do
                        {
                            Changeset changeset = changesets.Dequeue();
                            
                            // advance the client's current changeset # to the one in the message
                            if (changeset.ChangesetID > _currentChangesetId)
                            {
                                _currentChangesetId = changeset.ChangesetID;
                            }

                            ExecuteChangeset(changeset);

                        } while (changesets.Count != 0);
                    }
                }
                catch (Exception ex)
                {
                    // don't let this crash the app, but make sure to log the issue
                    DbLogger.Instance.Log(String.Format("Exception on changeset check: {0}", ex.Message));
                }
                finally
                {
                    _isCheckingForUpdates = false;
                }
            });
        }

        public event ChangesetReceivedEventHandler LockRecordReceived;
        public event ChangesetReceivedEventHandler UnlockRecordReceived;

        public event ChangesetReceivedEventHandler InsertRecordReceived;
        public event ChangesetReceivedEventHandler UpdateRecordReceived;
        public event ChangesetReceivedEventHandler DeleteRecordReceived;

        public event EventHandler DataImportedReceived;

        private void ExecuteChangeset(Changeset changeset)
        {
            switch (changeset.Type)
            {
                case ChangesetType.DataImported:
                    if (DataImportedReceived != null)
                    {
                        DataImportedReceived(this, new EventArgs());
                    }
                    break;
                case ChangesetType.DeleteRecord:
                    if (DeleteRecordReceived != null)
                    {
                        DeleteRecordReceived(this, new ChangesetReceivedEventArgs(changeset.RecordID, changeset.FormID));
                    }
                    break;
                case ChangesetType.InsertRecord:
                    if (InsertRecordReceived != null)
                    {
                        InsertRecordReceived(this, new ChangesetReceivedEventArgs(changeset.RecordID, changeset.FormID));
                    }
                    break;
                case ChangesetType.LockAllClientIsRefreshing:
                    break;
                case ChangesetType.LockRecord:
                    if (LockRecordReceived != null)
                    {
                        LockRecordReceived(this, new ChangesetReceivedEventArgs(changeset.RecordID, changeset.FormID));
                    }
                    break;
                case ChangesetType.UnlockAllClientRefreshComplete:
                    break;
                case ChangesetType.UnlockRecord:
                    if (UnlockRecordReceived != null)
                    {
                        UnlockRecordReceived(this, new ChangesetReceivedEventArgs(changeset.RecordID, changeset.FormID));
                    }
                    break;
                case ChangesetType.UpdateRecord:
                    if (UpdateRecordReceived != null)
                    {
                        UpdateRecordReceived(this, new ChangesetReceivedEventArgs(changeset.RecordID, changeset.FormID));
                    }
                    break;
            }
        }

        /// <summary>
        /// Starts checking the server for available updates
        /// </summary>
        public void StartPolling()
        {
            _updateTimer.Interval = Interval;
            _updateTimer.Start();
        }

        /// <summary>
        /// Stops checking the server for available updates
        /// </summary>
        public void StopPolling()
        {
            _updateTimer.Stop();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _updateTimer.Stop();
                _updateTimer.Elapsed -= Timer_Elapsed;
                _updateTimer.Dispose();
            }
            // free native resources if there are any.
        }
    }
}
